﻿// ***************************************************************************
// YAMM
// Copyright (C) 2008 Christian Schmiljun, Frank Weißenborn 
// http://www.codeplex.com/YAMM 
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// ***************************************************************************

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

using ICSharpCode.SharpZipLib.Zip;

using YAMM.PlugIn;

namespace YAMM
{
    /// <summary>
    /// Manage plugins.
    /// </summary>
    internal static class PluginManager
    {
        private static List<PlugIn.IPlugin> pluginList;

        public static List<PlugIn.IPlugin> PluginList
        {
            get
            {
                return pluginList;
            }
        }

        private static IServiceProvider serviceProvider = null;

        private static string pluginsFolder = System.IO.Path.Combine(Application.StartupPath, "plugins");
        private static string pluginsTempFolder = System.IO.Path.Combine(Application.StartupPath, "plugins_temp");

        internal static string PluginsFolder
        {
            get { return pluginsFolder; }
        }

        /// <summary>
        /// Initializes the <see cref="PluginManager"/> class.
        /// </summary>
        /// <param name="serviceProvider">The service provider for the plugins.</param>
        public static void Init(IServiceProvider serviceProvider)
        {
            if (PluginManager.serviceProvider != null)
                // wurde schon initialisiert
                throw new Exception("PluginManager wurde schon initialisiert");
            if (serviceProvider == null)
                throw new ArgumentNullException("serviceProvider");
            PluginManager.serviceProvider = serviceProvider;
            ReloadPlugins();
        }
        
        /// <summary>
        /// Reloads the plugins in folder "$(TargetDir)\plugins".
        /// </summary>
        private static void ReloadPlugins()
        {
            pluginList = new List<YAMM.PlugIn.IPlugin>();
            string path = pluginsFolder;
            if (!System.IO.Directory.Exists(path))
            {
                System.IO.Directory.CreateDirectory(path);
            }
            List<string> temp = new List<string>();
            foreach (string folder in Directory.GetDirectories(path))
            {
                temp.AddRange(Directory.GetFiles(folder, "*.dll"));
            }
            temp.AddRange(Directory.GetFiles(path, "*.dll"));
            string[] pluginFiles = temp.ToArray();
                        

            for (int i = 0; i < pluginFiles.Length; i++)
            {
                string args = pluginFiles[i].Substring(
                    pluginFiles[i].LastIndexOf("\\") + 1,
                    pluginFiles[i].IndexOf(".dll") -
                    pluginFiles[i].LastIndexOf("\\") - 1);

                Type ObjType = null;
                //IPlugin ipi;
                // load the dll
                try
                {
                    // load it
                    Assembly ass = null;
                    ass = Assembly.LoadFile(System.IO.Path.Combine(path, pluginFiles[i]));
                    //ass = Assembly.Load(args);
                    if (ass != null)
                    {
                        Type[] t = ass.GetTypes();

                        for (int k = 0; k < t.Length; k++)
                        {
                            Type[] interfaces = t[k].GetInterfaces();
                            for (int j = 0; j < interfaces.Length; j++)
                            {
                                if (interfaces[j].Equals(typeof(PlugIn.IPlugin)))
                                {
                                    try
                                    {
                                        if (t[k].IsPublic && t[k].IsClass && (!t[k].IsAbstract) && (!t[k].IsGenericType))
                                        {
                                            IPlugin pl = (IPlugin)Activator.CreateInstance(t[k]);
                                            pluginList.Add(pl);
                                            pl.Load(serviceProvider);
                                            pl.Send += new DataEventHandler(PluginManager_Send);
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        Console.WriteLine(ex.Message);
                                    }
                                }
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }

        static void PluginManager_Send(object sender, object data, EventArgs e)
        {
            //TODO ???
            throw new NotImplementedException();
            Selection sel = new Selection();
            //Abfrage welches Plugin aus pluginList die Daten verarbeiten kann mit canReceive(object)
            for (int i = 0; i < pluginList.Count; i++)
            {
                if (pluginList[i].CanReceive(data.GetType()))
                {
                    sel.AddItem(pluginList[i]);
                }
            }
            //Anzeige einer Liste
            if (sel.ShowDialog() == DialogResult.OK)
            {
                //weiterleiten der Daten durch Receive(object)
                sel.SelectedItem.Receive(data);
            }
        }

        public static bool InstallPlugin(string file)
        {
            if (File.Exists(file))
            {                
                switch (new FileInfo(file).Extension)
                {
                    case ".dll":                        
                        return DllInstall(file);                        
                    case ".zip":                        
                        return ZipInstall(file);
                    default:
                        return false;
                }
            }
            return false;
        }
       
        private static Guid CheckDllForPlugin(string file)
        {
            try
            {
                // load Assembly
                Assembly assembly = Assembly.LoadFile(file);
                // find IPlugin
                Type[] typesInAssembly = assembly.GetTypes();
                Type ipluginType = null;
                foreach (Type item in typesInAssembly)
                {
                    if (ipluginType != null)
                        break;
                    Type[] interfaces = item.GetInterfaces();
                    for (int j = 0; j < interfaces.Length; j++)
                    {
                        if (interfaces[j].Equals(typeof(PlugIn.IPlugin)))
                        {
                            if (item.IsPublic && item.IsClass && (!item.IsAbstract) && (!item.IsGenericType))
                            {
                                ipluginType = item;
                                break;
                            }
                        }
                    }
                }
                if (ipluginType != null)
                {
                    return ((IPlugin)Activator.CreateInstance(ipluginType)).Guid;
                }
                return Guid.Empty;
            }
            catch
            {
                return Guid.Empty;
            }
        }

        private static bool UnZipFile(string zipFilePath, string folder)
        {
            bool retVal = true;
            try
            {
                if (File.Exists(zipFilePath))
                {
                    using (ZipInputStream zipStream = new ZipInputStream(File.OpenRead(zipFilePath)))
                    {
                        // remove, create temp folder for unzipped files
                        if (!Directory.Exists(folder))
                        {
                            Directory.CreateDirectory(folder);                                                     
                        }
                        
                        
                        ZipEntry theEntry;
                        while ((theEntry = zipStream.GetNextEntry()) != null)
                        {
                            if (theEntry.IsFile)
                            {
                                if (theEntry.Name != "")
                                {
                                    string strNewFile = Path.Combine(folder, theEntry.Name);
                                    if (File.Exists(strNewFile))
                                    {
                                        File.Delete(strNewFile);
                                    }
                                    using (FileStream streamWriter = File.Create(strNewFile))
                                    {
                                        int size = 2048;
                                        byte[] data = new byte[2048];
                                        while (true)
                                        {
                                            size = zipStream.Read(data, 0, data.Length);
                                            if (size > 0)
                                                streamWriter.Write(data, 0, size);
                                            else
                                                break;
                                        }
                                        streamWriter.Close();
                                    }
                                }
                            }
                            else if (theEntry.IsDirectory)
                            {
                                string strNewDirectory = Path.Combine(folder, theEntry.Name);
                                if (!Directory.Exists(strNewDirectory))
                                {
                                    Directory.CreateDirectory(strNewDirectory);
                                }
                            }
                        }
                        zipStream.Close();
                    }
                }
            }
            catch
            {
                retVal = false;
            }
            return retVal;
        }

        private static bool ZipInstall(string file)
        {
            string tempDirectory = Path.Combine(pluginsTempFolder, Guid.NewGuid().ToString());

            if (UnZipFile(file, tempDirectory))
            {
                // seek in pluginsTempFolder for assemblys (dll, exe)
                List<string> dotNetFiles = new List<string>();
                dotNetFiles.AddRange(Directory.GetFiles(tempDirectory, "*.exe", SearchOption.TopDirectoryOnly));
                dotNetFiles.AddRange(Directory.GetFiles(tempDirectory, "*.dll", SearchOption.TopDirectoryOnly));
                Guid guid = Guid.Empty;
                foreach (string item in dotNetFiles)
                {
                    // check if dll is a plugin
                    Guid t = CheckDllForPlugin(item);
                    if (t == null || t == Guid.Empty)
                    {
                    }
                    else
                    {
                        guid = t;
                        break;
                    }
                }
                if (guid == null || guid == Guid.Empty)
                    return false;
                else
                {
                    try
                    {
                        // copy to plugin folder, if not exist - else abort
                        string folder = Path.Combine(pluginsFolder, guid.ToString());
                        if (Directory.Exists(folder))
                        {
                            //MessageBox.Show("Plugin ist schon installiert. Deinstallieren Sie es ggf. zuvor.", "", MessageBoxButtons.OK, MessageBoxIcon.Stop);
                            return false;
                        }
                        Tools.Directory.Copy(tempDirectory, folder);                        
                        return true;
                    }
                    catch
                    {
                        return false;
                    }
                }

            }
            return false;
        }

        private static bool DllInstall(string file)
        {
            Guid guid = CheckDllForPlugin(file);
            if (guid == null || guid == Guid.Empty)
                return false;
            else
            {
                try
                {
                    // copy to plugin folder                    
                    string folder = Path.Combine(pluginsFolder, guid.ToString());
                    Directory.CreateDirectory(folder);
                    File.Copy(file, Path.Combine(folder, new FileInfo(file).Name), true);                    
                    return true;
                }
                catch 
                {
                    return false;
                }
            }
        }
    }
}
